"""
sqlite3 连接池实现

在Python中如果对象定义了__del__方法的话，在对象的引用记数为0时会自动调用__del__方法，
但如果A对象引用B对象，B对象又引用A对象，就形成循环引用，此时A，B对象引用次数都为1。
python就无法正常调用__del__方法，原计划在__del__方法里释放的资源自然也就无法释放。

一个连接池拥有多个连接，而每个连接又拥有这个连接池的实例(一个叫pool的属性)。这样就产生了循环引用的问题。
解决办法：在每次从池中获取连接的时候将连接的pool设置为当前实例，然后在归还这个连接的时候再将其设置为None，
并且要在这个连接对象的__del__方法中将pool属性设置为None。
"""

import abc
import six
import queue


class PoolExecution(Exception):
    pass


@six.add_metaclass(abc.ABCMeta)
class PoolConnection(object):
    def __init__(self, **config):
        """
        对象初始化操作
        :param config:
        """
        self.conn = None
        self.config = config
        self.pool = None

    def __del__(self):
        """
        对象删除操作,在对象的引用记数为0时会自动调用__del__方法
        :return:
        """
        self.release()

    def __enter__(self):
        """上下文管理入口"""
        pass

    def __exit__(self, exc_type, exc_val, exc_tb):
        """上下文管理结尾"""
        self.close()

    @abc.abstractmethod
    def _create_conn(self, **config):
        pass

    def __getattr__(self, item):
        """
        如果属性查找(attribute lookup)在实例以及对应的类中(通过__dict__)失败,
        那么会调用到类的__getattr__函数,如果没有定义这个函数,那么抛出AttributeError异常。
        :param item:
        :return:
        """
        if self.conn is None and self.pool is not None:
            self.conn = self._create_conn(**self.config)
        if self.conn is None:
            raise PoolExecution("can't create db conn or conn is closed")
        return getattr(self.conn, item)

    def release(self):
        print('release pool connection')
        if self.conn is not None:
            self.conn.close()
            self.conn = None
        self.pool = None

    def close(self):
        if self.pool is None:
            raise PoolExecution('connection is closed')
        self.pool.free(self)


class Pool(object):
    def __init__(self, max_active=20, max_wait=None, init_size=0, db_type="SQLite3", **config):
        self.__free_conns = queue.Queue(max_active)
        self.max_wait = max_wait
        self.db_type = db_type
        self.config = config
        init_size = max_active if init_size > max_active else init_size
        # 初始化连接池,数据库连接的个数,默认为0
        for i in range(init_size):
            self.free(self._create_conn())

    def __del__(self):
        """
        删除连接池对象
        :return:
        """
        print('__del__ poll')
        self.release()

    def release(self):
        """
        释放资源,关闭池中的所有连接
        :return:
        """
        print('release pool connection')
        while self.__free_conns and not self.__free_conns.empty():
            con = self.get()
            con.release()
        self.__free_conns = None

    def _create_conn(self):
        """
        创建连接
        :return:
        """
        if self.db_type in dbcs:
            return dbcs[self.db_type](**self.config)

    def get(self, timeout=None):
        """
        获取一个连接
        :param timeout: 超时时间
        :return:
        """
        if timeout is None:
            timeout = self.max_wait
        # 如果队列为空,就直接创建一个连接
        if self.__free_conns.empty():
            conn = self._create_conn()
        else:
            conn = self.__free_conns.get(timeout=timeout)
        print(self.__free_conns.qsize())
        conn.pool = self
        return conn

    def free(self, conn):
        """
        释放连接到池中
        :param conn: 连接对象
        :return:
        """
        conn.pool = None
        if self.__free_conns.full():
            # 如果连接池已经满了,直接关闭连接
            conn.release()
            return
        else:
            self.__free_conns.put_nowait(conn)


class SQLit3PoolConnection(PoolConnection):
    def _create_conn(self, **config):
        import sqlite3
        return sqlite3.connect(**config)


dbcs = {"SQLite3": SQLit3PoolConnection}

if __name__ == '__main__':
    pool = Pool(database="D:\\test.db")
    conn = pool.get()
    with conn:
        for a in conn.execute("SELECT sqlite_version()"):
            print(a)